/*
 * 易族智汇（北京）科技有限公司 版权所有。
 * 未经许可，您不得使用此文件。
 * 官方地址：www.javamall.com.cn
 */
package com.enation.app.javashop.service.trade.order.impl;

import com.enation.app.javashop.client.goods.GoodsClient;
import com.enation.app.javashop.client.goods.GoodsQuantityClient;
import com.enation.app.javashop.framework.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.enation.app.javashop.framework.util.StringUtil;
import com.enation.app.javashop.mapper.trade.order.OrderItemsMapper;
import com.enation.app.javashop.mapper.trade.order.OrderMapper;
import com.enation.app.javashop.mapper.trade.order.TradeMapper;
import com.enation.app.javashop.model.base.CachePrefix;
import com.enation.app.javashop.model.base.message.OrderStatusChangeMsg;
import com.enation.app.javashop.model.base.rabbitmq.AmqpExchange;
import com.enation.app.javashop.model.errorcode.TradeErrorCode;
import com.enation.app.javashop.model.goods.enums.QuantityType;
import com.enation.app.javashop.model.goods.vo.GoodsQuantityVO;
import com.enation.app.javashop.model.goods.vo.GoodsSkuVO;
import com.enation.app.javashop.model.promotion.tool.enums.PromotionTypeEnum;
import com.enation.app.javashop.model.trade.cart.vo.CartPromotionVo;
import com.enation.app.javashop.model.trade.order.dos.*;
import com.enation.app.javashop.model.trade.order.dto.OrderDTO;
import com.enation.app.javashop.model.trade.order.enums.OrderStatusEnum;
import com.enation.app.javashop.model.trade.order.enums.PayStatusEnum;
import com.enation.app.javashop.model.trade.order.enums.PaymentTypeEnum;
import com.enation.app.javashop.model.trade.order.enums.TradeStatusEnum;
import com.enation.app.javashop.model.trade.order.vo.OrderSkuVO;
import com.enation.app.javashop.model.trade.order.vo.TradeVO;
import com.enation.app.javashop.service.trade.order.TradeIntodbManager;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.rabbitmq.MessageSender;
import com.enation.app.javashop.framework.rabbitmq.MqMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 交易入库业务类
 * @author: liuyulei
 * @create: 2020/3/20 15:29
 * @version:1.0
 * @since: 7.2.0
 **/
@Service
public class TradeIntodbManagerImpl implements TradeIntodbManager {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private Integer orderCacheTimeout = 60 * 60;

    @Autowired
    private TradeMapper tradeMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemsMapper orderItemsMapper;

    @Autowired
    private Cache cache;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private GoodsQuantityClient goodsQuantityClient;

    /**
     * 入库处理
     * @param tradeVO 交易VO
     */
    @Override
    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED,
            rollbackFor = {RuntimeException.class, ServiceException.class, Exception.class})
    public void intoDB(TradeVO tradeVO) {
        if (tradeVO == null) {
            throw new RuntimeException("交易无法入库，原因：trade为空");
        }
        //交易单状态
        String tradeStatus = OrderStatusEnum.CONFIRM.value();
        // 交易入库
        TradeDO tradeDO = new TradeDO(tradeVO);

        //循环订单信息进行扣减订单商品库存操作
        for (OrderDTO orderDTO: tradeVO.getOrderList()) {

            //扣减库存并获取扣减库存结果
            boolean bool = lockStock(orderDTO);

            //订单信息入库
            this.orderIntoDB(orderDTO, bool);

            //一笔交易中，只要有一笔订单扣减库存失败，那么这笔交易单的状态就是出库失败
            //这样做是防止在某个订单扣减库存失败时，买家依然可以对整笔交易进行支付
            if (!bool) {
                tradeStatus = TradeStatusEnum.INTODB_ERROR.value();
            }
        }

        tradeDO.setTradeStatus(tradeStatus);
        tradeMapper.insert(tradeDO);

        //将交易VO放入缓存，失效时间为1小时
        String cacheKey = getTradeCacheKey(tradeVO.getTradeSn());
        this.cache.put(cacheKey, tradeVO, orderCacheTimeout);
        //发送订单创建消息
        this.messageSender.send(new MqMessage(AmqpExchange.ORDER_CREATE,
                AmqpExchange.ORDER_CREATE + "_ROUTING",
                cacheKey));

    }

    /**
     * 订单信息入库
     *
     * @param order     订单信息
     * @param lockStock 扣减库存结果
     */
    private void orderIntoDB(OrderDTO order, boolean lockStock) {
        //将DTO转换DO
        OrderDO orderDO = new OrderDO(order);

        //根据订单编号获取订单扣减库存结果
        Double orderPrice = order.getOrderPrice();
        //如果当前订单扣减库存失败，则修改订单状态为出库失败
        if (!lockStock) {
            orderDO.setOrderStatus(OrderStatusEnum.INTODB_ERROR.value());
        } else{
            if (orderPrice == 0 && !order.getPaymentType().equals(PaymentTypeEnum.COD.value())) {
                //如果锁库存成功，并且待支付金额为0，则状态变成已付款
                orderDO.setOrderStatus(OrderStatusEnum.PAID_OFF.value());
                orderDO.setPayStatus(PayStatusEnum.PAY_YES.value());
            }else {
                //否则订单是已确认状态（锁库成功）
                orderDO.setOrderStatus(OrderStatusEnum.CONFIRM.value());
            }
        }

        if (StringUtil.isEmpty(orderDO.getSn())) {
            throw new ServiceException(TradeErrorCode.E452.name(), "订单编号为空无法创建订单");
        }

        //订单信息入库
        orderMapper.insert(orderDO);

        //订单项入库
        for (OrderSkuVO skuVO : order.getOrderSkuList()) {
            GoodsSkuVO goodsSkuVO = this.goodsClient.getSkuFromCache(skuVO.getSkuId());

            OrderItemsDO item = new OrderItemsDO(goodsSkuVO);
            item.setOrderSn(orderDO.getSn());
            item.setTradeSn(orderDO.getTradeSn());
            item.setNum(skuVO.getNum());
            item.setPrice(skuVO.getPurchasePrice());

            //判断是否为团购优惠
            List<CartPromotionVo> singleList = skuVO.getSingleList();
            if (singleList != null && !singleList.isEmpty()) {
                for (CartPromotionVo cartPromotionVo : singleList) {
                    if (PromotionTypeEnum.GROUPBUY.toString().equals(cartPromotionVo.getPromotionType())) {
                        item.setPromotionType(cartPromotionVo.getPromotionType());
                        item.setPromotionId(cartPromotionVo.getPromotionId());
                        break;
                    }
                }
            }
            orderItemsMapper.insert(item);
        }

        //订单mq消息
        OrderStatusChangeMsg orderStatusChangeMsg = new OrderStatusChangeMsg();
        orderStatusChangeMsg.setOrderDO(orderDO);
        orderStatusChangeMsg.setOldStatus(OrderStatusEnum.valueOf(orderDO.getOrderStatus()));
        orderStatusChangeMsg.setNewStatus(OrderStatusEnum.valueOf(orderDO.getOrderStatus()));
        orderStatusChangeMsg.setOrderDTO(order);
        this.messageSender.send(new MqMessage(AmqpExchange.ORDER_STATUS_CHANGE, AmqpExchange.ORDER_STATUS_CHANGE + "_ROUTING", orderStatusChangeMsg));
    }

    /**
     * 订单商品锁库（扣减库存）操作
     *
     * @param order 订单信息
     * @return
     */
    private boolean lockStock(OrderDTO order) {
        //获取订单中的商品sku信息集合
        List<OrderSkuVO> skuList = order.getOrderSkuList();
        //根据订单中的商品sku信息集合构建要扣减的库存列表
        List<GoodsQuantityVO> goodsQuantityList = buildQuantityList(skuList);

        //扣减正常库存，注意：如果不成功，库存在脚本里已经回滚，程序不需要回滚
        boolean lockStockResult = goodsQuantityClient.updateSkuQuantity(goodsQuantityList);
        logger.debug("订单【" + order.getSn() + "】普通商品锁库存结果为：" + lockStockResult);

        return lockStockResult;
    }

    /**
     * 获取交易缓存key
     *
     * @param tradeSn
     * @return
     */
    public String getTradeCacheKey(String tradeSn) {
        //重新压入缓存
        String cacheKey = CachePrefix.TRADE_SESSION_ID_PREFIX.getPrefix() + tradeSn;
        return cacheKey;
    }

    /**
     * 根据订单中的商品sku信息集合构建要扣减的库存列表
     *
     * @param skuList sku信息集合
     * @return
     */
    private List<GoodsQuantityVO> buildQuantityList(List<OrderSkuVO> skuList) {
        List<GoodsQuantityVO> goodsQuantityList = new ArrayList<>();

        //循环商品sku集合，得到要扣减库存的商品信息
        for (OrderSkuVO sku : skuList) {
            GoodsQuantityVO goodsQuantity = new GoodsQuantityVO();
            goodsQuantity.setSkuId(sku.getSkuId());
            goodsQuantity.setGoodsId(sku.getGoodsId());
            //因为是扣减库存，所以用0减去购买数量，扣减的库存数设置为负数
            goodsQuantity.setQuantity(0 - sku.getNum());
            //下单扣减的商品库存是可用库存
            goodsQuantity.setQuantityType(QuantityType.enable);
            //放入集合中
            goodsQuantityList.add(goodsQuantity);
        }

        return goodsQuantityList;
    }

}
